using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.VisualStudio.Modeling.Diagrams;

namespace Microsoft.Modeling.StoryboardDesigner
{
    /// <summary>
    /// New type of compartment that displays a multi-line text field
    /// </summary>
    public partial class MultiLineCompartment
    {
        #region Delegate declarations
        /// <summary>
        /// Delegate that will be called to get the display value 
        /// in the compartment
        /// </summary>
        /// <param name="compartment"></param>
        /// <returns></returns>
        public delegate string GetDisplayValueDelegate(MultiLineCompartment compartment);

        /// <summary>
        /// Delegate
        /// </summary>
        /// <param name="compartment"></param>
        /// <param name="newValue"></param>
        public delegate void SetDisplayValueDelegate(MultiLineCompartment compartment, object newValue);
        #endregion

        #region Instance variables
        /// <summary>
        /// Ref to the field used to display the body text
        /// </summary>
        private DemoTextField bodyTextField;

        /// <summary>
        /// Minimum height in lines for the body text field
        /// </summary>
        private int _minHeightInLines;

        /// <summary>
        /// Delegate to get value to display in the text field
        /// </summary>
        private MultiLineCompartment.GetDisplayValueDelegate _getValueDelegate;

        /// <summary>
        /// Delegate to set value from text field
        /// </summary>
        private MultiLineCompartment.SetDisplayValueDelegate _setValueDelegate;

        #endregion

        #region Properties

        /// <summary>
        /// Gets / sets the minimum size of the compartment in lines of text.
        /// </summary>
        public int MinimumHeightInLines
        {
            get { return _minHeightInLines; }
            set { _minHeightInLines = value; }
        }

        #endregion

        /// <summary>
        /// Override to calculate the size required to 
        /// display the text in the field (i.e. so that
        /// the compartment expands as necessary).
        /// </summary>
        protected override SizeD ExpandedSize
        {
            get
            {
                SizeD size = base.ExpandedSize;
                size = GetRequiredSize();
                return size;
            }
        }

        private string BodyTextFieldName
        {
            get
            {
                return "bodyTextFieldName";
            }
        }

        public override string AccessibleName
        {
            get { return this.HeaderDisplayText; }
        }

        protected override void InitializeDecorators(IList<ShapeField> shapeFields, IList<Decorator> decorators)
        {
            base.InitializeDecorators(shapeFields, decorators);
        }

        /// <summary>
        /// Define the single text field displayed by the compartment
        /// </summary>
        /// <remarks>NB the shape fields collection is stored per class, 
        /// not per instance, so all instances of this compartment will
        /// share the same TextField.</remarks>
        protected override void InitializeShapeFields(IList<ShapeField> shapeFields)
        {
            base.InitializeShapeFields(shapeFields);

            // Add a single textfield
            TextField field = MultiLineFieldHelper.GetMultiLineTextField(this, this.BodyTextFieldName, false);

            // Make the field focusable so it can be edited.
            field.DefaultFocusable = true;

            // Associate the visibility of the field with the expand collapse decorator
            AssociatedPropertyInfo info = new AssociatedPropertyInfo(NodeShape.IsExpandedDomainPropertyId);
            info.IsShapeProperty = true;
            field.AssociateVisibilityWith(base.Store, info);

            // Always associate the text field with the same custom
            // property on this shape.
            AssociatedPropertyInfo displayInfo = new AssociatedPropertyInfo(MultiLineCompartment.CustomDisplayPropertyDomainPropertyId);
            displayInfo.IsShapeProperty = true;
            field.AssociateValueWith(this.Store, displayInfo);

            // Anchor the shape
            // Top: to the bottom of the header text region
            // Left and Right: to the parent
            // Bottom: to the parent.
            field.AnchoringBehavior.SetLeftAnchor(AnchoringBehavior.Edge.Left, 0);
            field.AnchoringBehavior.SetTopAnchor(base.HeaderBackgroundField, AnchoringBehavior.Edge.Bottom, 0);
            field.AnchoringBehavior.SetRightAnchor(AnchoringBehavior.Edge.Right, 0);
            field.AnchoringBehavior.SetBottomAnchor(AnchoringBehavior.Edge.Bottom, 0);

            // Sizing
            // Turn off autosize, and turn multiline on (can't
            // have both on at the same time - causes an exception)
            field.DefaultAutoSize = false;
            field.DefaultMultipleLine = true;
            field.AnchoringBehavior.MinimumHeightInLines = 2;

            // Store the field so we can refer to it later
            bodyTextField = field as DemoTextField;
            shapeFields.Add(field);
        }

        /// <summary>
        /// Allow the user to resize the height of the compartment
        /// </summary>
        public override NodeSides ResizableSides
        {
            get
            {
                NodeSides sides = NodeSides.Bottom;
                return sides;
            }
        }

        public override void InitializeFromDescription(CompartmentDescription description)
        {
            base.InitializeFromDescription(description);

            MultiLineCompartmentDescription mlDesc = description as MultiLineCompartmentDescription;

            _minHeightInLines = mlDesc.MinimumHeightInLines;
            _getValueDelegate = mlDesc.GetValueDelegate;
            _setValueDelegate = mlDesc.SetValueDelegate;
        }

        public override void UpdateSize()
        {
            // Get the size of the base shape
            SizeD baseSize = base.Size;

            // Get the desired size
            SizeD newSize = GetRequiredSize();

            // Update the size if necessary
            if(baseSize != newSize)
            {
                base.Size = newSize;
            }
        }

        internal SizeD GetRequiredSize()
        {
            // Get the size of the base shape
            SizeD baseSize = base.Size;

            if(!base.IsExpanded)
            {
                return baseSize;
            }

            // Now work out the new size
            // Keep the width the same as the base width
            SizeD newSize = new SizeD(baseSize.Width, 0);

            // The height should be the height of the header, plus
            // the spaced required to display the text
            newSize.Height = base.HeaderBounds.Height + (MultiLineFieldHelper.GetRequiredSizeForFixedWidth(this, BodyTextField).Height);

            // Enforce a minimum size
            SizeD minSize = this.MinimumSize;
            
            if(newSize.Height < minSize.Height)
            {
                newSize.Height = minSize.Height;
            }

            // Enforce the minimum height in lines
            if(_minHeightInLines > 0)
            {
                double minHeight =
                    0.05d +
                    base.HeaderBounds.Height +
                    BodyTextField.GetFontHeight(this) * _minHeightInLines;
                
                if(newSize.Height < minHeight)
                {
                    newSize.Height = minHeight;
                }
            }

            return newSize;
        }

        internal DemoTextField BodyTextField
        {
            get
            {
                if(this.bodyTextField == null)
                {
                    this.bodyTextField = base.FindShapeField(this.BodyTextFieldName) as DemoTextField;
                }

                Debug.Assert(bodyTextField != null, "Error - could not find bodyTextField");
                
                return this.bodyTextField;
            }
        }

        #region Display property handling
        /// <summary>
        /// Implementation of the "Get" method for the custom-storage
        /// property "CustomDisplay"
        /// </summary>
        /// <returns>Calls the "get" delegate supplied by the comaprtment mapping</returns>
        internal string GetCustomDisplayPropertyValue()
        {
            if(_getValueDelegate != null)
            {
                return _getValueDelegate(this);
            }

            return Properties.Resources.NoDelegate;
        }

        /// <summary>
        /// Implementation of the "Set" method for the custom-storage
        /// property "CustomDisplay"
        /// </summary>
        /// <remarks>Calls the "set" delegate supplied by the compartment mapping.</remarks>
        internal void SetCustomDisplayPropertyValue(string newValue)
        {

            if(_setValueDelegate != null)
            {
                _setValueDelegate(this, newValue);
            }
        }
        #endregion
    }
}